/** <!--------------------------------------------------------------------------------------------->
 * @file RevGraphItem.cpp
 *
 * GSvn - A Graphical Client for SVN
 * Copyright (C) 2012  Karl Krach, gsvn@bluespirit.la
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 <!---------------------------------------------------------------------------------------------> */

// -- includes --
#include "RevGraphItem.h"
#include "RevGraphItemMap.h"
#include "RevGraphItemLink.h"
#include "RevGraphBranch.h"
#include <QPainter>
#include <QGraphicsScene>
#include <QGraphicsItemGroup>


/** <!--------------------------------------------------------------------------------------------->
 * Creates the RevGraphItem object
 *
 * @param pCenterRevID (in) revision of the centered item
 * @param iRevID       (in) revision id of this graph item
 <!---------------------------------------------------------------------------------------------> */
RevGraphItem::RevGraphItem( int* pCenterRevID, int iRevID )
    : m_iRevID(iRevID), m_pCenterRevID(pCenterRevID), m_pModelItem(NULL), m_pGlobalLink(NULL),
      m_pBranchLink(NULL), m_bCopyInfoRegistered(false)
{
	// center of the universe
	setPos( 0, 0 );

	setToolTip( QString("Empty with ID %1").arg(iRevID) );

}	// END RevGraphItem( int )

/** <!--------------------------------------------------------------------------------------------->
 * Creates the RevGraphItem object with the given model item
 *
 * @param pCenterRevID (in) revision of the centered item
 * @param pModelItem   (in) model item to visualize
 <!---------------------------------------------------------------------------------------------> */
RevGraphItem::RevGraphItem( int* pCenterRevID, QSP<RevModelItem> pModelItem )
    : m_iRevID(pModelItem->m_iRevision), m_pCenterRevID(pCenterRevID), m_pModelItem(pModelItem),
      m_pGlobalLink(NULL), m_pBranchLink(NULL), m_bCopyInfoRegistered(false)
{
	setToolTip( createToolTipString() );

}	// END RevGraphItem( QSP<RevModelItem> )

/** <!--------------------------------------------------------------------------------------------->
 * Deletes the RevGraphItem object
 <!---------------------------------------------------------------------------------------------> */
RevGraphItem::~RevGraphItem()
{
}	// END ~RevGraphItem()

/** <!--------------------------------------------------------------------------------------------->
 * Sets the data of the given model item to be visualized.
 *
 * @param pModelItem   (in) model item to visualize
 <!---------------------------------------------------------------------------------------------> */
void RevGraphItem::setData( QSP<RevModelItem> pModelItem )
{
	if( pModelItem->m_iRevision != m_iRevID )
		throw QString( "Cannot set data of different ref ID!" );

	m_pModelItem = pModelItem;
	m_bCopyInfoRegistered = false;

	setToolTip( createToolTipString() );

	update();

}	// END setData( QSP<RevModelItem> )

/** <!--------------------------------------------------------------------------------------------->
 * Sets the branch-link which gives the item access to all other items in this branch (e.g. to its
 * pre- and successor)
 *
 * @param rBranchLink (in) branch link to set
 <!---------------------------------------------------------------------------------------------> */
void RevGraphItem::setBranchLink( const RevGraphItemLink& rBranchLink )
{
	m_pBranchLink = new RevGraphItemLink(rBranchLink);

}	// END setBranchLink( QMap<int, RevGraphItem*>::iterator )

/** <!--------------------------------------------------------------------------------------------->
 * Sets the global-link which gives the item access to all other items
 *
 * @param rPosition (in) global link to set
 <!---------------------------------------------------------------------------------------------> */
void RevGraphItem::setGlobalLink( const RevGraphItemLink& rLink )
{
	Q_ASSERT( m_pGlobalLink == NULL );
	m_pGlobalLink = new RevGraphItemLink(rLink);

	// Move element to the bottom
	if( m_iRevID >= *m_pCenterRevID )
	{
		if( m_pGlobalLink->hasNext() )
		{
			RevGraphItemLink oNext = m_pGlobalLink->getNext();

			//
			// Set to y-position of 'next item'
			//
			setPos( x(), (*oNext)->y() );

			//
			// Move all next by item height + space
			//
			QList<QGraphicsItem *> aAllNext = oNext.getAllNext();
			QGraphicsItemGroup* pGroup = scene()->createItemGroup( aAllNext );
			pGroup->moveBy( 0, S_FULL_HEIGHT + S_SPACE );
			scene()->destroyItemGroup( pGroup );
		}
		else if( m_pGlobalLink->hasPrevious() )
		{
			RevGraphItemLink oPrev = m_pGlobalLink->getPrevious();

			//
			// Set current behind last element
			//
			QPointF oPrevPos( (*oPrev)->pos() );
			setPos( x(), oPrevPos.y() + S_FULL_HEIGHT + S_SPACE );
		}
		// if the map is empty - I'm the first element :)
		else
		{
			// Nothing more to do
		}
	}
	// Move element to the top
	else if( m_iRevID < *m_pCenterRevID )
	{
		if( m_pGlobalLink->hasPrevious() )
		{
			RevGraphItemLink oPrev = m_pGlobalLink->getPrevious();

			//
			// Set y-position of 'previous item'
			//
			setPos( x(), (*oPrev)->y() );

			//
			// Move all previous by item height + space
			//
			QList<QGraphicsItem *> aAllPrevious = oPrev.getAllPrevious();
			QGraphicsItemGroup* pGroup = scene()->createItemGroup( aAllPrevious );
			pGroup->moveBy( 0, -S_FULL_HEIGHT - S_SPACE );
			scene()->destroyItemGroup( pGroup );
		}
		else if( m_pGlobalLink->hasNext() )
		{
			RevGraphItemLink oNext = m_pGlobalLink->getNext();

			//
			// Set current behind last element
			//
			QPointF oNextPos( (*oNext)->pos() );
			setPos( x(), oNextPos.y() - S_FULL_HEIGHT - S_SPACE );
		}
		// if the map is empty - I'm the first element :)
		else
		{
			// Nothing more to do
		}
	}
}	// END setGlobalLink( const RevGraphItemMap::iterator& )

/** <!--------------------------------------------------------------------------------------------->
 * Creates the tooltip string
 *
 * @return the created tooltip string
 <!---------------------------------------------------------------------------------------------> */
QString RevGraphItem::createToolTipString() const
{
	if( m_pModelItem.isNull() )
		return QString();

	QString strCopyInfoString = "false\n";
	if( !m_pModelItem->m_aCopyInfo.isEmpty() )
	{
		strCopyInfoString = "";
		foreach( const SvnHistoryListener::SvnCopyInfo& rCopyInfo, m_pModelItem->m_aCopyInfo )
		{
			strCopyInfoString += QString("from %1:%2\n").arg(rCopyInfo.m_strFromPath)
			                                            .arg(rCopyInfo.m_iFromRev);
		}
	}

	return QString("Branch Index=%1\nRevision=%2\nTime=%3\nCopy Info=%4Author=%5\nMessage=%6")
	                .arg(m_pModelItem->m_iBranchIndex)
	                .arg(m_pModelItem->m_iRevision)
	                .arg(m_pModelItem->m_oDateTime.toString())
	                .arg(strCopyInfoString)
	                .arg(m_pModelItem->m_strAuthor)
	                .arg(m_pModelItem->m_strMessage);

}	// END createToolTipString()

/** <!--------------------------------------------------------------------------------------------->
 * Registers the copy info at this graph item
 *
 * @param aCopyInfo (in) copy info to register
 <!---------------------------------------------------------------------------------------------> */
void RevGraphItem::registerCopyInfo( const QList<SvnHistoryListener::SvnCopyInfo>& aCopyInfo ) const
{
	if( m_pGlobalLink == NULL || m_bCopyInfoRegistered )
		return;

	m_bCopyInfoRegistered = true;
	m_aBranchSource.clear();

	foreach( const SvnHistoryListener::SvnCopyInfo& rCopyInfo, aCopyInfo )
	{
		if( m_pGlobalLink->hasItem( rCopyInfo.m_iFromRev ) )
		{
			RevGraphItem* pSrcItem = m_pGlobalLink->getItem( rCopyInfo.m_iFromRev ).value();

			//
			// If it's a 'copy' in the same branch, it's in fact a 'rename'
			//
			if( pSrcItem->getBranchIndex() != getBranchIndex() )
			{
				m_aBranchSource << pSrcItem;
			}
			else
			{
				qDebug() << " source " << pSrcItem->getBranchIndex() << "vs mine" << getBranchIndex();
			}
		}
		else
		{
			qWarning() << "registerCopyInfo: Cannot find revision " << rCopyInfo.m_iFromRev;

			// Force second run, if revisions are still missing (still not loaded)
			m_bCopyInfoRegistered = false;
		}
	}
}	// END registerCopyInfo( const SvnHistoryListener::SvnCopyInfo& )

/** <!--------------------------------------------------------------------------------------------->
 * In difference to Qt's boundingRect() method, getItemBoundingRect() returns the bounding rect
 * of the item (without arrows, etc)
 *
 * @returns bounding rectangular of item
 <!---------------------------------------------------------------------------------------------> */
QRectF RevGraphItem::getItemBoundingRect() const
{
	QRectF oRect( S_FULL_WIDTH / -2.0f,
	              S_FULL_HEIGHT / -2.0f,
	              S_FULL_WIDTH, S_FULL_HEIGHT
	             );

	return oRect;

}	// END getItemBoundingRect() const

/** <!--------------------------------------------------------------------------------------------->
 * This function defines the outer bounds of the item as a rectangle; all painting must be
 * restricted to inside an item's bounding rect. QGraphicsView uses this to determine whether the
 * item requires redrawing.
 *
 * Note: For shapes that paint an outline / stroke, it is important to include half the pen
 * width in the bounding rect. It is not necessary to compensate for antialiasing, though.
 *
 * @returns the item's bounding rect
 <!---------------------------------------------------------------------------------------------> */
QRectF RevGraphItem::boundingRect() const
{
	//
	// Initialize bounding rect with graph item size
	//
	QRectF oRect = getItemBoundingRect();

	//
	// Add arrow to previous element
	//
	if( m_pBranchLink != NULL && m_pBranchLink->hasPrevious() )
	{
		RevGraphItem* pPrevious = m_pBranchLink->getPrevious().value();
		QPointF oPrevPos = mapFromItem( parentItem(), pPrevious->pos() );
		oRect.setY( oPrevPos.y() + S_FULL_HEIGHT / 2.0f );
	}

	//
	// Add branch arrow
	//
	if( ! m_pModelItem.isNull() && (!m_bCopyInfoRegistered || !m_aBranchSource.isEmpty()) )
	{
		if( !m_bCopyInfoRegistered )
		{
			registerCopyInfo( m_pModelItem->m_aCopyInfo );
		}

		foreach( const RevGraphItem* pBranchSrc, m_aBranchSource )
		{
			QPointF oBranchItemPos = mapFromItem( pBranchSrc->parentItem(), pBranchSrc->pos() );
			oRect = oRect.united( QRectF( oBranchItemPos, oBranchItemPos + QPointF(1,1) ) );
		}
	}

	return oRect;

}	// END boundingRect() const

/** <!--------------------------------------------------------------------------------------------->
 * This function, which is usually called by QGraphicsView, paints the contents of an item in
 * local coordinates.
 *
 * @param pPainter (in) painter to paint the graphics item
 * @param pOption  (in) provides the style for the item (such as its state, exposed area, etc)
 * @param pWidget  (in) points to the widget that is being painted on
 <!---------------------------------------------------------------------------------------------> */
void RevGraphItem::paint( QPainter* pPainter, const QStyleOptionGraphicsItem* pOption,
                           QWidget* pWidget )
{
	Q_UNUSED(pOption);
	Q_UNUSED(pWidget);

	pPainter->save();

	QRectF oItemRect( S_FULL_WIDTH / -2.0f,
	                  S_FULL_HEIGHT / -2.0f,
	                  S_FULL_WIDTH, S_FULL_HEIGHT
	                );
	if( hasFocus() )
	{
		oItemRect.translate( -3, -3 );
	}

	paintItemRect( pPainter, oItemRect );
	paintItemText( pPainter, oItemRect );
	paintPrevArrow( pPainter );

	pPainter->restore();

//	// TODO: DEBUG DEBUG DEBUG
//	// TODO: DEBUG DEBUG DEBUG
//	// TODO: DEBUG DEBUG DEBUG
//	// TODO: DEBUG DEBUG DEBUG
//	if( getRevisionID() == 8 || getRevisionID() == 4 )
//	{
//		pPainter->save();
//		pPainter->setPen( QColor(Qt::darkGreen) );
//		pPainter->setBrush( QBrush(QColor(Qt::green), Qt::FDiagPattern));
//		pPainter->drawRect( boundingRect().adjusted( -5, -5, 5, 5 ) );
//		pPainter->restore();
//	}
//	// TODO: DEBUG DEBUG DEBUG
//	// TODO: DEBUG DEBUG DEBUG
//	// TODO: DEBUG DEBUG DEBUG

}	// END paint( QPainter*, const QStyleOptionGraphicsItem*, QWidget* )

/** <!--------------------------------------------------------------------------------------------->
 * Paints the 'background' rectangular
 *
 * @param pPainter (in) painter to draw rect to
 * @param rRect    (in) rectangular position
 <!---------------------------------------------------------------------------------------------> */
void RevGraphItem::paintItemRect( QPainter* pPainter, const QRectF& rRect )
{
	// Paint items with copy info red
	pPainter->setBrush( m_aBranchSource.isEmpty() ? QColor(83, 167, 226) : QColor( Qt::red ) );
	pPainter->setPen( Qt::NoPen );

	float fRadius = (S_FULL_WIDTH + S_FULL_HEIGHT) / 20.0f;
	pPainter->drawRoundedRect( rRect, fRadius, fRadius);

}	// END paintItemRect( const QRectF& )

/** <!--------------------------------------------------------------------------------------------->
 * Paints the item text
 *
 * @param pPainter (in) painter to draw text to
 * @param rRect    (in) position to paint the text to
 <!---------------------------------------------------------------------------------------------> */
void RevGraphItem::paintItemText( QPainter* pPainter, const QRectF& rRect )
{

	pPainter->setBrush( Qt::NoBrush );
	pPainter->setPen( QColor( Qt::black ) );

	QString strText = QString( "%1\ndata requested..." ).arg( m_iRevID );
	if( !m_pModelItem.isNull() )
	{
		strText = QString( "r%1\n%3\n%4" )
		           .arg(m_pModelItem->m_iRevision)
		           .arg(m_pModelItem->m_strAuthor)
		           .arg(m_pModelItem->m_oDateTime.toString("yyyy-MM-dd hh:mm"));
	}
	pPainter->drawText( rRect.adjusted( rRect.width()* 0.03, rRect.height()* 0.03,
	                                    rRect.width()*-0.03, rRect.height()*-0.03), Qt::AlignCenter,
	                    strText );

}	// END paintItemText( const QRectF& )

/** <!--------------------------------------------------------------------------------------------->
 * Paints the arrow to the previous item (also branching arrows)
 *
 * @param pPainter (in) painter to draw arrow on
 <!---------------------------------------------------------------------------------------------> */
void RevGraphItem::paintPrevArrow( QPainter* pPainter )
{
	if( m_pBranchLink != NULL )
	{
		//
		// Paint arrow to previous item
		//
		if( m_pBranchLink->hasPrevious() )
		{
			RevGraphItem* pPrevious = m_pBranchLink->getPrevious().value();
			paintLineTo( pPainter, pPrevious, Qt::gray );
		}
		//
		// Paint arrow to items, where this rev branches from (shouldn't, but can be more than one)
		//
		else if( ! m_pModelItem.isNull() && (!m_bCopyInfoRegistered || !m_aBranchSource.isEmpty()) )
		{
			if( !m_bCopyInfoRegistered )
			{
				registerCopyInfo( m_pModelItem->m_aCopyInfo );
			}

			foreach( const RevGraphItem* pBranchSrc, m_aBranchSource )
			{
				paintLineTo( pPainter, pBranchSrc, Qt::red, true );
			}
		}
	}
}	// END paintPrevArrow()

/** <!--------------------------------------------------------------------------------------------->
 * Paints an arrow to the given item
 *
 * @param pPainter    (in) painter to draw arrow on
 * @param pTargetItem (in) item to draw the arrow to
 * @param eColor      (in) color of the arrow
 * @param bWithArrow  (in) true, of paint an arrow
 <!---------------------------------------------------------------------------------------------> */
void RevGraphItem::paintLineTo( QPainter* pPainter, const RevGraphItem* pTargetItem,
                                Qt::GlobalColor eColor, bool bWithArrow )
{
	pPainter->setBrush( Qt::NoBrush );
	pPainter->setPen(   QColor( eColor ) );

	int iArrowSize = bWithArrow ? 7 : 0;

	QPointF oArrowStart, oArrowEnd, oPointOfSupport;
	QRectF oTargetRect = mapFromItem(pTargetItem, pTargetItem->getItemBoundingRect()).boundingRect();

	//
	// Target on the right of this
	//
	if( oTargetRect.left() > S_FULL_WIDTH / 2.0f )
	{
		// Place start at the right of target
		oArrowStart = mapFromItem( pTargetItem->parentItem(), pTargetItem->pos() ) +
		                           QPointF( S_FULL_WIDTH / 2.0f, 0 );
		// Target below of this
		if( oTargetRect.top() < -S_FULL_HEIGHT / 2.0f )
		{
			oArrowEnd = mapFromItem( parentItem(), pos() ) -
			                         QPointF( 0, S_FULL_HEIGHT / 2.0f + iArrowSize );
		}
		// Target above of this
		else
		{
			oArrowEnd = mapFromItem( parentItem(), pos() ) +
			                         QPointF( 0, S_FULL_HEIGHT / 2.0f + iArrowSize );
		}
		oPointOfSupport = QPointF( oArrowEnd.x(), oArrowStart.y() );
	}
	//
	// Target on the left of this
	//
	else if( oTargetRect.right() < -S_FULL_WIDTH / 2.0f )
	{
		// Place start on the left of target
		oArrowStart = mapFromItem( pTargetItem->parentItem(), pTargetItem->pos() ) -
		                           QPointF( S_FULL_WIDTH / 2.0f, 0 );
		// Target below of this
		if( oTargetRect.top() < -S_FULL_HEIGHT / 2.0f )
		{
			oArrowEnd = mapFromItem( parentItem(), pos() ) -
			                         QPointF( 0, S_FULL_HEIGHT / 2.0f + iArrowSize );
		}
		// Target above of this
		else
		{
			oArrowEnd = mapFromItem( parentItem(), pos() ) +
			                         QPointF( 0, S_FULL_HEIGHT / 2.0f + iArrowSize );
		}
		oPointOfSupport = QPointF( oArrowEnd.x(), oArrowStart.y() );
	}
	//
	// Target is below or above item
	//
	else
	{
		//
		// Target below of this
		//
		if( oTargetRect.top() < -S_FULL_HEIGHT / 2.0f )
		{
			// Place start at the bottom of target
			oArrowStart = mapFromItem( pTargetItem->parentItem(), pTargetItem->pos() ) +
			                           QPointF( 0, S_FULL_HEIGHT / 2.0f );
			oArrowEnd   = mapFromItem( parentItem(), pos() ) -
			                           QPointF( 0, S_FULL_HEIGHT / 2.0f + iArrowSize );
			oPointOfSupport = oArrowEnd;
		}
		//
		// Target above of this
		//
		else
		{
			// Place start at the top of target
			oArrowStart = mapFromItem( pTargetItem->parentItem(), pTargetItem->pos() ) -
			                           QPointF( 0, S_FULL_HEIGHT / 2.0f );
			oArrowEnd   = mapFromItem( parentItem(), pos() ) +
			                           QPointF( 0, S_FULL_HEIGHT / 2.0f + iArrowSize );
			oPointOfSupport = oArrowEnd;
		}
	}

	QPainterPath oPath;
	oPath.moveTo(  oArrowStart );
	oPath.cubicTo( oArrowStart.x(),     oArrowStart.y(),
	               oPointOfSupport.x(), oPointOfSupport.y(),
	               oArrowEnd.x(),       oArrowEnd.y() );

	pPainter->drawPath( oPath );

	if( bWithArrow )
	{
		pPainter->setBrush( QColor( eColor ) );
		pPainter->setPen(   QColor( eColor ) );

		const QPointF aArrowPoints[3] = {
			oArrowEnd - QPointF(  iArrowSize,           0 ),
			oArrowEnd - QPointF(           0, -iArrowSize ),
			oArrowEnd - QPointF( -iArrowSize,           0 ),
		};

		pPainter->drawPolygon( aArrowPoints, 3 );
	}

}	// END paintLineTo( QPainter*, RevGraphItem* )
